import { ConstantEnum } from '@libs/common/enums';
import { DingNotificationPersonalPointsEvent } from '@libs/common/event-emitter';
import {
  BaseRepository,
  indexPaginateBuilder,
  showBuilderEntity,
} from '@libs/common/repository';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { plainToInstance } from 'class-transformer';
import { groupBy } from 'lodash';
import { DataSource, In } from 'typeorm';

import { DepartmentConfigService } from '@/dingding/department-config/department-config.service';
import { PointsHistoryService } from '@/market/points-history/points-history.service';

import {
  CreatePointsWaitDto,
  FindPointsWaitDto,
  IndexPointsWaitDto,
  SendPointsWaitDto,
  ShowPointsWaitDto,
  UpdatePointsWaitDto,
} from './dto/points-wait.dto';
import { PointsWait } from './entities/points-wait.entity';

@Injectable()
export class PointsWaitService extends BaseRepository {
  constructor(
    @Inject(ConstantEnum.DB_DATA_SOURCE_MANAGER) dataSource: DataSource,
  ) {
    super(dataSource);
  }

  protected repository = this.getRepository(PointsWait);

  protected repositoryManager = () => this.getRepository(PointsWait);

  @Inject(PointsHistoryService)
  private readonly pointsHistoryService: PointsHistoryService;

  @Inject(DepartmentConfigService)
  private readonly departmentConfigService: DepartmentConfigService;

  @Inject(EventEmitter2)
  private readonly eventEmitter: EventEmitter2;

  async index(dto: IndexPointsWaitDto) {
    const res = await indexPaginateBuilder({
      dto,
      repository: this.repository,
    });
    return res;
  }

  show<D extends ShowPointsWaitDto = ShowPointsWaitDto>(dto: D) {
    return showBuilderEntity(this.repository, dto);
  }

  update(dto: UpdatePointsWaitDto | UpdatePointsWaitDto[]) {
    const dtos = Array.isArray(dto) ? dto : [dto];
    const entities = plainToInstance(PointsWait, dtos);

    return this.repositoryManager().save(entities);
  }

  find(dto?: FindPointsWaitDto) {
    return this.repository.find({
      where: dto || {},
    });
  }

  // 发放积分
  async sendPoints(dto: SendPointsWaitDto, user: SC.User.SysInfo) {
    const query: FindPointsWaitDto = { status: 0, type: dto.type };

    if (dto.ids?.length) query.id = In(dto.ids);
    if (dto.departmentId) query.departmentId = dto.departmentId;

    // 查询没有发放的记录
    const undistributed = await this.find(query);

    if (undistributed.length === 0) return;

    // 获取积分变动钉钉通知的部门
    const departmentConfig = await this.departmentConfigService.find({
      status: 1,
      addPointsNotice: 1,
    });

    const departmentConfigMap = groupBy(departmentConfig, 'id');

    const users: Record<
      string,
      {
        userId: string;
        userName: string;
        points: number;
        dingId: string;
        notification: boolean;
      }
    > = {};

    undistributed.forEach((log) => {
      if (users[log.userId]) {
        users[log.userId].points! += log.points;
      } else {
        users[log.userId] = {
          userId: log.userId,
          userName: log.userName,
          points: log.points,
          dingId: log.dingId,
          notification: departmentConfigMap[log.departmentId]?.length > 0,
        };
      }
    });

    // 发放
    for (const item of Object.values(users)) {
      const history = await this.pointsHistoryService.attendanceCreate({
        userId: item.userId,
        userName: item.userName,
        points: item.points,
        dingId: item.dingId,
        operatorId: user.id,
        operatorName: user.realname!,
        desc: dto.desc,
        type: dto.type,
      });

      const id = history.id;
      const userPoints = history.userPoints.points;

      if (item.notification) {
        // 通知钉钉
        this.eventEmitter.emit(
          DingNotificationPersonalPointsEvent.key,
          new DingNotificationPersonalPointsEvent({
            addPoints: item.points,
            points: userPoints,
            userDingId: item.dingId,
            pointsHistoryId: id,
            desc: dto.desc,
          }),
        );
      }
    }

    // 修改状态
    await this.repositoryManager().save(
      undistributed.map((v) => ({
        ...v,
        status: 1,
        approverName: user.username,
        approverId: user.id,
      })),
      {
        chunk: 100,
      },
    );

    return undistributed.length;
  }

  create(dto: CreatePointsWaitDto | CreatePointsWaitDto[]) {
    const entity = this.repositoryManager().create(
      plainToInstance(PointsWait, dto),
    );

    return this.repositoryManager().save(entity, {
      chunk: 500,
    });
  }
}
